/*
  微信小程序 watch 监听 配置同 vue2
  依赖  es6+ Proxy Reflect 对象
  支持 Page Component 构造器
  支持 deep 深度监听 - 支持 delete 运算符
  支持 immediate 立即执行
  watch prop Function 的 this 指向 当前 Page | Component 实例
  示例:
    Page({
      data:{
        age: 18,
        person:{
          name:"test"
        }
      },
      watch:{
        age(val , oldVal){
          console.log("ag change" , val , oldVal)
        },
        person:{
          deep:true,
          handler(val){
            console.log("person change" , val)
          }
        }
      }
    })
*/
const comUtil = require('@/utils/common')
const mainUtil = require('@/utils/main')
const watchConfig = require('./config')
// 获取 proxy handle
function getReactHandle(source, observeKey, observeConfig) {
  const proxyTarget = Reflect.get(source, observeKey)
  const { handler, deep, immediate, _immediateInit } = observeConfig
  if (immediate && !_immediateInit) {
    handler(proxyTarget, proxyTarget)
    observeConfig._immediateInit = true
  }
  return {
    get(target, key) {
      // deep watch
      if (deep) {
        if (typeof target[key] === 'object' && target[key] !== null) {
          return new Proxy(target[key], getReactHandle(source, observeKey, observeConfig))
        }
      }
      return Reflect.get(target, key)
    },
    set(target, key, value) {
      // 监听代理是否设置完成
      // if (vm._watchProxySetted) {
      const oldValue = Reflect.get(target, key)
      // 找不到老值，新增
      // 老值和新值不相等，修改
      if (!oldValue || oldValue !== value) {
        if (handler && typeof handler === 'function') {
          // proxy Array length 改变会触发 set
          if (!(Array.isArray(target) && key === 'length')) {
            wx.nextTick(() => {
              handler(proxyTarget, proxyTarget)
            })
          }
        }
      }
      return Reflect.set(target, key, value)
    },
    deleteProperty(target, key) {
      if (handler && typeof handler === 'function') {
        // 利用 Object.prototype.hasOwnProperty 判断 属性是否被删除过
        if (comUtil.hasOwn(target, key)) {
          wx.nextTick(() => {
            handler(proxyTarget, proxyTarget)
          })
        }
      }
      return Reflect.deleteProperty(target, key)
    }
  }
}

// 对引用类型进行拦截
function setReact(source, observeKey, observeConfig) {
  const proxyTarget = source[ observeKey ]
  source[ observeKey ] = new Proxy(proxyTarget, getReactHandle(source, observeKey, observeConfig))
}

// 对原始类型进行拦截
function setRef(source, observeKey, observeConfig) {
  // console.log(source[ observeKey ])
  const { handler, immediate, _immediateInit } = observeConfig
  let val = source[ observeKey ]
  if (immediate && _immediateInit) {
    handler(val, val)
    observeConfig._immediateInit = true
  }
  Object.defineProperty(source, observeKey, {
    configurable: false,
    enumerable: true,
    get() {
      return val
    },
    set(newVal) {
      // 监听代理是否设置完成
      const oldVal = source[ observeKey ]
      if (oldVal !== newVal && handler && typeof handler === 'function') {
        wx.nextTick(() => {
          handler(newVal, oldVal)
        })
      }
      val = newVal
      return val
    }
  })
}

// 设置追踪
function setEffect(source, observeKey, observeConfig) {
  if (source && observeKey) {
    const sourceObserveVal = source[ observeKey ]
    const typeofObserveVal = typeof sourceObserveVal
    // null undefined string number boolean 可Json序列化原始类型
    if (sourceObserveVal === null ||
        sourceObserveVal === undefined ||
        typeofObserveVal === 'string' ||
        typeofObserveVal === 'number' ||
        typeofObserveVal === 'boolean'
    ) {
      // 原始类型
      setRef(source, observeKey, observeConfig)
    } else {
      // 如果是引用类型 设置 react 监听
      setReact(source, observeKey, observeConfig)
    }
  }
}

// 设置代理
function setWatch(isPage = true) {
  // 是否开启 watch
  if (!watchConfig.openWatchAuto) {
    return
  }
  // 是否初始化过 watch
  if (this[ watchConfig.watchIsConfigableKey ]) {
    return
  }
  let watchOption
  // page watch option
  if (isPage) {
    watchOption = this[ watchConfig.watchKey_page ]
  } else {
    // component watch option
    const _watchOption = this.data[ watchConfig.watchKey_component ]
    this[ watchConfig.watchKey_component ] = _watchOption
    watchOption = _watchOption
    delete this.data[ watchConfig.watchKey_component ]
  }
  // 设置pageData proxy 代理
  if (watchOption && typeof watchOption === 'object') {
    const watchKeys = Object.keys(watchOption)
    if (watchKeys.length) {
      // 所有拦截器是否设置完毕
      watchKeys.forEach(watchKeyPath => {
        // 监视设置参数合并
        const watchConfig = watchOption[ watchKeyPath ]
        const watchConfigType = comUtil.toType(watchConfig)
        const nextWatchConfig = {}
        if (watchConfigType === 'Function') {
          nextWatchConfig.handler = watchConfig
        } else if (watchConfigType === 'Object') {
          Object.assign(nextWatchConfig, watchConfig)
        } else {
          throw Error('Watch config params type must be Function or Object')
        }
        // 设置hanlder 绑定 this 实例
        if (nextWatchConfig.handler && typeof nextWatchConfig.handler === 'function') {
          nextWatchConfig.handler = nextWatchConfig.handler.bind(this)
          nextWatchConfig.vm = this
        } else {
          // 如果没有 handler 直接阻断
          return
        }
        // 序列化 model path
        const formatWatchKeyPath = mainUtil.formatModelPath(watchKeyPath)
        // 获取序列化 model Arr
        const formatWatchKeyPathArr = formatWatchKeyPath.split('.')
        // 设置追踪器
        if (formatWatchKeyPathArr.length) {
          let sourceTarget = this.data
          for (let i = 0, len = formatWatchKeyPathArr.length; i < len; i++) {
            const nextModelPath = formatWatchKeyPathArr[i]
            if (i === len - 1) {
              setEffect(sourceTarget, nextModelPath, nextWatchConfig)
            } else {
              sourceTarget = sourceTarget[ nextModelPath ]
            }
          }
        }
      })
    }
  }
  // 添加配置完成标记
  // 如果外部属性写入导致冲突
  if (!comUtil.hasOwn(this, watchConfig.watchIsConfigableKey)) {
    // 设置 page 一个变量表示 setData 已经被配置过  不可配置 不可写入
    Object.defineProperty(this, watchConfig.watchIsConfigableKey, {
      value: true,
      configurable: false,
      writable: false
    })
  } else {
    throw Error(
      `open Watch , Page or Component Instance key ${watchConfig.watchIsConfigableKey}  are not configurable`
    )
  }
}

module.exports = setWatch
